package dal

import (
	"context"
	"fmt"
	"time"

	"gorm.io/plugin/dbresolver"

	"code.byted.org/gopkg/logs/v2/log"
	"github.com/spf13/cast"

	"code.byted.org/bytertc/whiteboard_server/biz/dal/ddl"
	"code.byted.org/bytertc/whiteboard_server/biz/dal/spec"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"github.com/pkg/errors"
)

const (
	Category_File_Transcode = iota + 1
	Category_Signal_Record
	Category_Video_Record
)
const (
	Switch_OFF = iota
	Switch_ON
)
const (
	Category_Switch_Transcode = iota + 1
	Category_Switch_Record
)
const (
	Space_Type_TOS = iota + 1
	Space_type_VOD
)

// 开关key
func genSwitchKey(unique spec.ConsoleBaseUnique) string {
	return fmt.Sprintf("consloe_switch:%s:%d",
		unique.GetAppId(), unique.GetCategory())
}
func genConfigItemKey(unique spec.ConsoleBaseUnique) string {
	return fmt.Sprintf("consloe_item:%s:%d",
		unique.GetAppId(), unique.GetCategory())
}

func (r *repo) GetConfigs(ctx context.Context, appId string, parentCategory int) ([]ddl.ConsoleConfig, error) {
	var cc []ddl.ConsoleConfig
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TableConsoleConfigs).
		Where("app_id = ? and deleted = 0", appId)
	if parentCategory > 0 {
		query = query.Where("parent_category = ?", parentCategory)
	}
	err := query.Find(&cc).Error
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		return nil, biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}

	return cc, nil
}

func (r *repo) SetConfig(ctx context.Context, unique spec.ConsoleItemUnique) error {
	if unique.GetCategory() != Category_File_Transcode && unique.GetCategory() != Category_Signal_Record && unique.GetCategory() != Category_Video_Record {
		biz_error.InvalidParam.Wrap(errors.Errorf("switch GetCategory invaid:%d", unique.GetCategory()))
	}
	if unique.GetParentCategory() != Category_Switch_Transcode && unique.GetParentCategory() != Category_Switch_Record {
		biz_error.InvalidParam.Wrap(errors.Errorf("switch GetParentCategory invaid:%d", unique.GetParentCategory()))
	}
	config := &ddl.ConsoleConfig{
		AppId:          unique.GetAppId(),
		ParentCategory: unique.GetParentCategory(),
		Category:       unique.GetCategory(),
		SpaceType:      unique.GetSpaceType(),
		Space:          unique.GetSpace(),
		AccountId:      unique.GetAccountId(),
		Region:         unique.GetRegion(),
	}
	err := r.db.WithContext(ctx).Table(ddl.TableConsoleConfigs).Create(config).Error
	if IsDuplicateError(err) {
		return biz_error.Conflict.Wrap(errors.WithStack(err))
	}
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	return nil
}

func (r *repo) GetConfig(ctx context.Context, unique spec.ConsoleBaseUnique, all bool) ([]ddl.ConsoleConfig, error) {
	var cc []ddl.ConsoleConfig
	m, err := r.cache.HGetAll(ctx, genConfigItemKey(unique))
	if err != nil {
		log.V1.CtxError(ctx, "GetConfig from cache err:%+v", err)
	}
	if len(m) > 0 {
		config, err := r.item2Struct(ctx, m)
		if err == nil {
			return []ddl.ConsoleConfig{config}, nil
		}
		log.V1.CtxError(ctx, "GetConfig item2Struct err:%+v", err)

	}
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TableConsoleConfigs).
		Where("app_id = ? and category = ?", unique.GetAppId(), unique.GetCategory())
	if !all {
		query = query.Where("deleted = 0")
	}
	err = query.Find(&cc).Error
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		return nil, biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	if len(cc) > 0 {
		_, err = r.cache.HMSet(ctx, genConfigItemKey(unique), r.item2Map(ctx, cc[0]))
		if err != nil {
			log.V1.CtxError(ctx, "GetConfig HMSet err:%+v,key:%s", err, genConfigItemKey(unique))
		} else {
			_, err = r.cache.Expire(ctx, genConfigItemKey(unique), time.Minute*10)
			if err != nil {
				log.V1.CtxError(ctx, "GetConfig case Expire err:%+v,key:%s", err, genConfigItemKey(unique))
			}
		}

	}
	return cc, nil
}

func (r *repo) UpdateConfig(ctx context.Context, unique spec.ConsoleItemUnique) error {
	err := r.cache.Del(ctx, genConfigItemKey(unique))
	if err != nil && !IsNotFoundError(err) {
		return biz_error.CacheError.Wrap(errors.WithStack(err))
	}
	m := map[string]interface{}{
		"space_type": unique.GetSpaceType(),
		"space":      unique.GetSpace(),
		"deleted":    0,
		"account_id": unique.GetAccountId(),
		"region":     unique.GetRegion(),
	}
	err = r.db.WithContext(ctx).Table(ddl.TableConsoleConfigs).
		Where("app_id = ? and category = ?",
			unique.GetAppId(), unique.GetCategory()).
		Updates(m).Error
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	return nil
}
func (r *repo) DeleteConfig(ctx context.Context, unique spec.ConsoleBaseUnique) error {
	err := r.cache.Del(ctx, genConfigItemKey(unique))
	if err != nil && !IsNotFoundError(err) {
		return biz_error.CacheError.Wrap(errors.WithStack(err))
	}
	err = r.db.WithContext(ctx).Table(ddl.TableConsoleConfigs).
		Where("app_id = ? and category = ?",
			unique.GetAppId(), unique.GetCategory()).
		Update("deleted", 1).Error
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	return nil
}
func (r *repo) GetSwitchInfos(ctx context.Context, unique spec.ConsoleBaseUnique) ([]ddl.ConsoleSwitch, error) {
	var cs []ddl.ConsoleSwitch
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TableConsoleSwitchs).
		Where("app_id = ?", unique.GetAppId())
	err := query.Find(&cs).Error
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		return nil, biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}

	return cs, nil
}

func (r *repo) GetSwitchInfo(ctx context.Context, unique spec.ConsoleBaseUnique) ([]ddl.ConsoleSwitch, error) {
	var cs []ddl.ConsoleSwitch
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TableConsoleSwitchs).
		Where("app_id = ? and category = ?", unique.GetAppId(), unique.GetCategory())
	err := query.Find(&cs).Error
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		return nil, biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	return cs, nil
}

func (r *repo) UpdateSwitchInfo(ctx context.Context, unique spec.ConsoleSwitchUnique) error {
	if unique.GetOpenStatus() != Switch_ON && unique.GetOpenStatus() != Switch_OFF {
		return biz_error.InvalidParam.Wrap(errors.Errorf("switch status invaid:%d", unique.GetOpenStatus()))
	}
	err := r.cache.Del(ctx, genSwitchKey(unique))
	if err != nil && !IsNotFoundError(err) {
		return biz_error.CacheError.Wrap(errors.WithStack(err))
	}
	err = r.db.WithContext(ctx).Table(ddl.TableConsoleSwitchs).
		Where("app_id = ? and category = ?",
			unique.GetAppId(), unique.GetCategory()).
		Update("status", unique.GetOpenStatus()).Error
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	return nil
}

func (r *repo) CreateSwitchInfo(ctx context.Context, unique spec.ConsoleSwitchUnique) error {
	if unique.GetCategory() != Category_Switch_Transcode && unique.GetCategory() != Category_Switch_Record {
		return biz_error.InvalidParam.Wrap(errors.Errorf("switch GetCategory invaid:%d", unique.GetCategory()))
	}
	info := &ddl.ConsoleSwitch{
		AppId:    unique.GetAppId(),
		Category: unique.GetCategory(),
		Status:   unique.GetOpenStatus(),
	}
	err := r.db.WithContext(ctx).Table(ddl.TableConsoleSwitchs).Create(info).Error
	if IsDuplicateError(err) {
		return biz_error.Conflict.Wrap(errors.WithStack(err))
	}
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	return nil
}

func (r *repo) GetSwitchFromCache(ctx context.Context, unique spec.ConsoleSwitchUnique) (int, error) {
	ret, err := r.cache.Get(ctx, genSwitchKey(unique))
	if err != nil {
		if !IsNotFoundError(err) {
			log.V1.CtxError(ctx, "GetSwitchFromCache from redis err:%+v", err)
		}
	} else {
		return cast.ToInt(ret), nil
	}
	infos, err := r.GetSwitchInfo(ctx, unique)
	if err != nil {
		log.V1.CtxError(ctx, "GetSwitchFromCache from mysql err:%+v", err)
		return Switch_OFF, err
	}
	if len(infos) == 0 {
		return Switch_OFF, nil
	}
	_, err = r.cache.Set(ctx, genSwitchKey(unique), cast.ToString(infos[0].Status), time.Minute*10)
	if err != nil {
		log.V1.CtxError(ctx, "GetSwitchFromCache Set err:%+v", err)
	}
	return infos[0].Status, nil

}

func (r *repo) item2Struct(ctx context.Context, m map[string]string) (ddl.ConsoleConfig, error) {
	config := ddl.ConsoleConfig{}
	if appId, ok := m["appId"]; ok {
		config.AppId = appId
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("appId not exist"))
	}
	if accountId, ok := m["accountId"]; ok {
		config.AccountId = accountId
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("account not exist"))
	}
	if parentCategory, ok := m["parentCategory"]; ok {
		config.ParentCategory = cast.ToInt(parentCategory)
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("parentCategory not exist"))
	}
	if category, ok := m["category"]; ok {
		config.Category = cast.ToInt(category)
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("category not exist"))
	}
	if spaceType, ok := m["spaceType"]; ok {
		config.SpaceType = cast.ToInt(spaceType)
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("spaceType not exist"))
	}
	if space, ok := m["space"]; ok {
		config.Space = space
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("space not exist"))
	}
	if region, ok := m["region"]; ok {
		config.Region = region
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("account not exist"))
	}
	if deleted, ok := m["deleted"]; ok {
		config.Deleted = cast.ToInt(deleted)
	} else {
		return config, biz_error.CacheError.Wrap(errors.Errorf("deleted not exist"))
	}
	return config, nil

}

func (r *repo) item2Map(ctx context.Context, config ddl.ConsoleConfig) map[string]interface{} {
	m := map[string]interface{}{
		"appId":          config.AppId,
		"accountId":      config.AccountId,
		"parentCategory": cast.ToString(config.ParentCategory),
		"category":       cast.ToString(config.Category),
		"spaceType":      cast.ToString(config.SpaceType),
		"space":          config.Space,
		"region":         config.Region,
		"deleted":        cast.ToString(config.Deleted),
	}
	return m
}
